/*
	* Copyright (C) GTA:Multiplayer Team (https://wiki.gta-mp.net/index.php/Team)
	*
	* Redistribution and use in source and binary forms, with or without
	* modification, are permitted provided that the following conditions are
	* met:
	*
	*     * Redistributions of source code must retain the above copyright
	*		notice, this list of conditions and the following disclaimer.
	*     * Redistributions in binary form must reproduce the above
	*		copyright notice, this list of conditions and the following disclaimer
	*		in the documentation and/or other materials provided with the
	*		distribution.
	*     * Neither the name of GTA-Network nor the names of its
	*		contributors may be used to endorse or promote products derived from
	*		this software without specific prior written permission.
	*
	* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
	* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
	* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
	* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT
	* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
	* DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND ON ANY
	* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
	* INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE
	* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <Windows.h>
#include <stdint.h>
#include <cstdio>
#include <Psapi.h>

// ScriptHookV
#include "scripthookv/main.h"
#include "scripthookv/natives.h"
#pragma comment(lib, "lib/scripthookv.lib")

// Find Pattern
bool CompareMemory(const uint8_t* pData, const uint8_t* bMask, const char* sMask)
{
	for (; *sMask; ++sMask, ++pData, ++bMask)
		if (*sMask == 'x' && *pData != *bMask)
			return false;

	return *sMask == NULL;
}
intptr_t FindPattern(const char* bMask, const char* sMask)
{
	// Game Base & Size
	static intptr_t pGameBase = (intptr_t) GetModuleHandle(nullptr);
	static uint32_t pGameSize = 0;
	if (!pGameSize)
	{
		MODULEINFO info;
		GetModuleInformation(GetCurrentProcess(), (HMODULE)pGameBase, &info, sizeof(MODULEINFO));
		pGameSize = info.SizeOfImage;
	}

	// Scan
	for (uint32_t i = 0; i < pGameSize; i++)
		if (CompareMemory((uint8_t*)(pGameBase + i), (uint8_t*)bMask, sMask))
			return pGameBase + i;

	return 0;
}

// Map Notification
void PushMapNotification(char* msg)
{
	UI::_SET_NOTIFICATION_TEXT_ENTRY("STRING");
	UI::_ADD_TEXT_COMPONENT_STRING(msg);
	UI::_DRAW_NOTIFICATION(FALSE, FALSE);
}

// Snow
void EnableSnow(bool bEnable)
{
	// Adresses
	static auto addr1 = FindPattern("\x80\x3D\x00\x00\x00\x00\x00\x74\x27\x84\xC0", "xx?????xxxx");
	static auto addr2 = FindPattern("\x44\x38\x3D\x00\x00\x00\x00\x74\x0F", "xxx????xx");

	// Outdated
	// In future the patterns might change
	if (!addr1 || !addr2)
	{
		PushMapNotification("Snow Mod is ~r~not compatible ~s~with this GTA Version!");
		return;
	}

	// Original Memory
	static uint8_t original1[10] = { 0 };
	static uint8_t original2[10] = { 0 };

	// Initialize
	static bool bInitialized = false;
	if (!bInitialized)
	{
		bInitialized = true;

		// Unprotect Memory
		VirtualProtect((void*)addr1, 9, PAGE_EXECUTE_READWRITE, nullptr);
		VirtualProtect((void*)addr2, 9, PAGE_EXECUTE_READWRITE, nullptr);

		// Copy original Memory
		memcpy(&original1, (void*)addr1, 9);
		memcpy(&original2, (void*)addr2, 9);
	}

	// Toggle
	if (bEnable)
	{
		// Weather
		GAMEPLAY::SET_WEATHER_TYPE_NOW_PERSIST("XMAS");

		// NOP checks
		memset((void*)addr1, 0x90, 9);
		memset((void*)addr2, 0x90, 9);

		// Notification
		PushMapNotification("Snow ~g~ON");
	}
	else
	{
		// Copy original memory
		memcpy((void*)addr1, &original1, 9);
		memcpy((void*)addr2, &original2, 9);

		// Weather
		GAMEPLAY::CLEAR_WEATHER_TYPE_PERSIST();
		GAMEPLAY::SET_WEATHER_TYPE_NOW("SUNNY");

		// Notification
		PushMapNotification("Snow ~r~OFF");
	}
}

// Main
void ScriptMain()
{
	bool bSnowEnabled = false;

	for (;;)
	{
		if (GetAsyncKeyState(VK_NUMPAD7) & 1)
		{
			bSnowEnabled = !bSnowEnabled;
			EnableSnow(bSnowEnabled);
		}

		WAIT(1);
	}
}

// Main
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, LPVOID pReserved)
{
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		// Register
		scriptRegister(hModule, ScriptMain);
		break;
	case DLL_PROCESS_DETACH:
		// Unregister
		scriptUnregister(ScriptMain);
		break;
	}

	return TRUE;
}